#!/usr/bin/env python

import operator
from datetime import datetime, timedelta

"""dateutils is a collection of datetime convenience functions."""

def get_datetime_from_timestamp(ts):
    """Returns a datetime object given a timestamp.

    ts: a timestamp in "%Y-%m-%d %H:%M:%S" format.  Look at the docs
        for time.strftime for exact definitions.  An example would be:
        "1983-05-28 08:13:26"

    """
    d, t = ts.split()
    year, month, day = [int(x) for x in d.split('-')]
    hour, minute, second = [int(x) for x in t.split(':')]
    return datetime(year, month, day, hour, minute, second)

def get_timestamp_from_datetime(dt):
    """Returns a timestamp from a datetime object.

    dt: a datetime object.

    """
    return dt.strftime("%Y-%m-%s %H:%M:%S")

def get_current_datetime():
    """Returns a datetime object representing now."""
    return datetime.now()

def get_current_timestamp():
    """Returns a timestamp representing now."""
    return get_timestamp_from_datetime(get_current_datetime())

def _truncate(dt, level):
    """Truncates part of a datetime object.

    dt:    a datetime object.
    level: a string representing up to what level to truncate.

    Supported levels are: seconds, minutes, hours, days, months, years.

    """
    if level == 'seconds':
        return datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute,
                        dt.second)
    elif level == 'minutes':
        return datetime(dt.year, dt.month, dt.day, dt.hour, dt.minute, 0)
    elif level == 'hours':
        return datetime(dt.year, dt.month, dt.day, dt.hour, 0, 0)
    elif level == 'days':
        return datetime(dt.year, dt.month, dt.day)
    elif level == 'months':
        return datetime(dt.year, dt.month, 1)
    elif level == 'years':
        return datetime(dt.year, 1, 1)

def _quantize(dt, level, intervals, prefer_negative=False):
    """Quantizes a datetime object.

    dt:              a datetime object.
    level:           a string representing the level to quantize.
    interval:        a list of ints representing values that 'level'
                     can be set to.
    prefer_negative: a boolean that, if set, will prefer negative
                     differences over positive differences in the case
                     that both are equal distances from the initial
                     value.

    Supported levels are: seconds, minutes, hours, days, months, years.

    """
    if level == 'seconds':
        dt = _truncate(dt, 'minutes')
        intervals = [timedelta(seconds=i) for i in intervals]
        intervals = [dt + x for x in intervals]
    elif level == 'minutes':
        dt = _truncate(dt, 'hours')
        intervals = [timedelta(minutes=i) for i in intervals]
        intervals = [dt + x for x in intervals]
    elif level == 'hours':
        dt = _truncate(dt, 'days')
        intervals = [timedelta(hours=i) for i in intervals]
        intervals = [dt + x for x in intervals]
    elif level == 'days':
        dt = _truncate(dt, 'months')
        intervals = [timedelta(days=i) for i in intervals]
        intervals = [dt + x for x in intervals]
    elif level == 'months':
        dt = _truncate(dt, 'years')
        intervals = [timedelta(months=i) for i in intervals]
        intervals = [dt + x for x in intervals]
    elif level == 'years':
        intervals = [datetime(year=x, month=1, day=1) for x in intervals]
    differences = []
    for i in intervals:
        d = timedelta_in_seconds(dt - i)
        differences.append((i, abs(d), d > 0))
    differences = sorted(differences, key=operator.itemgetter(1))[:2]
    if differences[0][1] == differences[1][1]:
        positive_difference = [x for x in differences if x[2]][0]
        negative_difference = [x for x in differences if not x[2]][0]
        if prefer_negative:
            return negative_difference[0]
        else:
            return positive_difference[0]
    else:
        return differences[0][0]

def truncate_to_seconds(dt):
    """Returns a datetime object representing the given second.

    dt: a datetime object.

    """
    return _truncate(dt, 'seconds')

def truncate_to_minutes(dt):
    """Returns a datetime object representing the given minute.

    dt: a datetime object.

    """
    return _truncate(dt, 'minutes')

def truncate_to_hours(dt):
    """Returns a datetime object representing the given hour.

    dt: a datetime object.

    """
    return _truncate(dt, 'hours')

def truncate_to_days(dt):
    """Returns a datetime object representing the given day.

    dt: a datetime object.

    """
    return _truncate(dt, 'days')

def truncate_to_months(dt):
    """Returns a datetime object representing the given month.

    dt: a datetime object.

    """
    return _truncate(dt, 'months')

def truncate_to_years(dt):
    """Returns a datetime object representing the given year.

    dt: a datetime object.

    """
    return _truncate(dt, 'years')

def quantize_to_seconds(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid seconds for the given datetime object.

    """
    return _quantize(dt, 'seconds', intervals)

def quantize_to_minutes(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid minutes for the given datetime object.

    """
    return _quantize(dt, 'minutes', intervals)

def quantize_to_hours(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid hours for the given datetime object.

    """
    return _quantize(dt, 'hours', intervals)

def quantize_to_days(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid days for the given datetime object.

    """
    return _quantize(dt, 'days', intervals)

def quantize_to_months(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid months for the given datetime object.

    """
    return _quantize(dt, 'months', intervals)

def quantize_to_years(dt, intervals):
    """Quantizes a datetime object to a set of intervals.

    dt:        a datetime object.
    intervals: a list of valid years for the given datetime object.

    """
    return _quantize(dt, 'years', intervals)

def timedelta_in_seconds(td):
    """Converts a timedelta instance to an integer of seconds.

    td: a timedelta instance.

    Warning: microseconds are discarded.

    """
    return (td.days * 86400) + td.seconds
